/*
    The NeonX Library

    Copyright (c) 2013 darkcat

    Code covered by the MIT License
*/

#pragma once

// nx...
#include "config/general.h"
// Traits
#include "type/typetraits.h"
// nx::limit_of
#include "utility/tools.h"
// Preprocessor
#include "macro/preprocessor.h"

// new, std::bad_alloc
#include <new>

//////////////////////////////////////////////////////////////////////////
NX_BEG
//////////////////////////////////////////////////////////////////////////

/*
    construct/destruct
*/

#define nx_construct(p, T, ...) \
    (T*)::new ((void*)(p)) T(__VA_ARGS__)

#define nx_construct_arr(p, T, N, ...) \
    do \
    { \
        for(size_t i = 0; i < N; ++i) \
            nx_construct(&(((T*)(p))[i]), T, __VA_ARGS__); \
    } while(false)

#define nx_destruct(p, T) \
    ((T*)(p))->~T()

#define nx_destruct_arr(p, T, N) \
    do \
    { \
        for(size_t i = 0; i < N; ++i) \
            nx_destruct(&(((T*)(p))[i]), T); \
    } while(false)

//////////////////////////////////////////////////////////////////////////

/*
    a adapter allocator for stl
*/

template <typename T, class Alloc_>
struct AllocAdapter
{
public:
    typedef typename nx_traits(T)::type_t   value_type;
    typedef typename nx_traits(T)::point_t  pointer;
    typedef typename nx_traits(T)::refer_t  reference;
    typedef typename nx_traits(T)::cpoint_t const_pointer;
    typedef typename nx_traits(T)::crefer_t const_reference;

    typedef size_t                          size_type;
    typedef ptrdiff_t                       difference_type;

    template <class U>
    struct rebind { typedef AllocAdapter<U, Alloc_> other; };

public:
    pointer address(reference val) const
    { return &val; }
    const_pointer address(const_reference val) const
    { return &val; }

    size_type max_size() const
    { return (nx::limit_of<size_type>() / sizeof(T)); }

public:
    AllocAdapter() {}
    AllocAdapter(const AllocAdapter<T, Alloc_>&) {}
    template <class U>
    AllocAdapter(const AllocAdapter<U, Alloc_>&) {}

    template <class U>
    AllocAdapter<T, Alloc_>& operator=(const AllocAdapter<U, Alloc_>&)
    {
        return (*this);
    }

    pointer allocate(size_type count, const pvoid = NX_NULL)
    {
        if (count > this->max_size()) throw std::bad_alloc();
        pvoid p = Alloc_::alloc(count * sizeof(T));
        if (!p) throw std::bad_alloc();
        return static_cast<pointer>(p);
    }
    void deallocate(pvoid p, size_type count)
    {
        Alloc_::free(p, count * sizeof(T));
    }

public:
	static void construct(pointer p)
	{
        nx_construct(p, value_type);
    }

    static void construct(pointer p, const T& val)
	{
        nx_construct(p, value_type, val);
    }

    static void destroy(pointer p)
    {
        nx_destruct(p, value_type);
    }
};

//////////////////////////////////////////////////////////////////////////

/*
    allocators
*/

struct SysAlloc
{
    static pvoid alloc  (size_t size);
    static void  free   (pvoid p, size_t size);

    template <typename T>
    struct Allocator { typedef AllocAdapter<T, SysAlloc> ret; };
};

#ifndef NX_ALLOC
#define NX_ALLOC    nx::SysAlloc
#endif

//////////////////////////////////////////////////////////////////////////

/*
    auto construct alloc
*/

inline pvoid alloc(size_t size)
{
    return NX_ALLOC::alloc(size);
}

namespace private_
{
    template <typename Alloc_, typename T>
    struct alloc_
    {
        static T* _(void)
        {
            return nx_construct(Alloc_::alloc(sizeof(T)), T);
        }

#   define NX_ALLOC_(i) \
        template <NX_PP_TYPE(i, typename P)> \
        static T* _(NX_PP_PARAM(i, P, & par)) \
        { \
            return nx_construct(Alloc_::alloc(sizeof(T)), T, NX_PP_TYPE(i, par)); \
        } \
        template <NX_PP_TYPE(i, typename P)> \
        static T* _(NX_PP_PARAM(i, const P, & par)) \
        { \
            return nx_construct(Alloc_::alloc(sizeof(T)), T, NX_PP_TYPE(i, par)); \
        }

        NX_MULTIPLE_MAX(NX_ALLOC_)

#   undef NX_ALLOC_
    };

    /* Make array to array pointer */
    template <typename Alloc_, typename T_, size_t N>
    struct alloc_<Alloc_, T_[N]>
    {
        typedef T_ T[N];
        static T* _(void)
        {
            T* p = (T*)Alloc_::alloc(sizeof(T));
            nx_construct_arr(*p, T_, N);
            return p;
        }

#   define NX_ALLOC_(i) \
        template <NX_PP_TYPE(i, typename P)> \
        static T* _(NX_PP_PARAM(i, P, & par)) \
        { \
            T* p = (T*)Alloc_::alloc(sizeof(T)); \
            nx_construct_arr(*p, T_, N, NX_PP_TYPE(i, par)); \
            return p; \
        } \
        template <NX_PP_TYPE(i, typename P)> \
        static T* _(NX_PP_PARAM(i, const P, & par)) \
        { \
            T* p = (T*)Alloc_::alloc(sizeof(T)); \
            nx_construct_arr(*p, T_, N, NX_PP_TYPE(i, par)); \
            return p; \
        }

        NX_MULTIPLE_MAX(NX_ALLOC_)

#   undef NX_ALLOC_
    };

    template <typename Alloc_>
    struct alloc_<Alloc_, void>
    {
        static void* _(size_t size)
        {
            return Alloc_::alloc(size);
        }
    };
}

template <typename T>
inline T* alloc(void)
{
    return private_::alloc_<NX_ALLOC, T>::_();
}

template <typename Alloc_, typename T>
inline T* alloc(void)
{
    return private_::alloc_<Alloc_, T>::_();
}

#define NX_ALLOC_(i) \
template <typename T, NX_PP_TYPE(i, typename P)> \
inline T* alloc(NX_PP_PARAM(i, P, & par)) \
{ \
    return private_::alloc_<NX_ALLOC, T>::_(NX_PP_TYPE(i, par)); \
} \
template <typename T, NX_PP_TYPE(i, typename P)> \
inline T* alloc(NX_PP_PARAM(i, const P, & par)) \
{ \
    return private_::alloc_<NX_ALLOC, T>::_(NX_PP_TYPE(i, par)); \
} \
template <typename Alloc_, typename T, NX_PP_TYPE(i, typename P)> \
inline T* alloc(NX_PP_PARAM(i, P, & par)) \
{ \
    return private_::alloc_<Alloc_, T>::_(NX_PP_TYPE(i, par)); \
} \
template <typename Alloc_, typename T, NX_PP_TYPE(i, typename P)> \
inline T* alloc(NX_PP_PARAM(i, const P, & par)) \
{ \
    return private_::alloc_<Alloc_, T>::_(NX_PP_TYPE(i, par)); \
}

NX_PP_MAX(NX_ALLOC_)

#undef NX_ALLOC_

/*
    auto destruct free
*/

inline void free(pvoid p, size_t size)
{
    NX_ALLOC::free(p, size);
}

template <typename Alloc_>
inline void free(pvoid p, size_t size)
{
    Alloc_::free(p, size);
}

template <typename T>
inline void free(T* p)
{
    if (!p) return;
    nx_destruct(p, T);
    NX_ALLOC::free(p, sizeof(T));
}

template <typename Alloc_, typename T>
inline void free(T* p)
{
    if (!p) return;
    nx_destruct(p, T);
    Alloc_::free(p, sizeof(T));
}

template <typename T, size_t N>
inline void free(T(* p)[N])
{
    if (!p) return;
    nx_destruct_arr(*p, T, N);
    NX_ALLOC::free(p, sizeof(T[N]));
}

template <typename Alloc_, typename T, size_t N>
inline void free(T(* p)[N])
{
    if (!p) return;
    nx_destruct_arr(*p, T, N);
    Alloc_::free(p, sizeof(T[N]));
}

//////////////////////////////////////////////////////////////////////////
NX_END
//////////////////////////////////////////////////////////////////////////
